/* 算法实现模板 */
import AlgorithmBase from "./AlgorithmBase";
import React from "react";
import ReactDom from 'react-dom';
import { loadDataset, txtToMatrix , mdRender} from "../../../common/Common";
import MatView2D, {createRandomPointStyleDict} from "../../../visual/MatView2D";
import Mat from "../../../common/Mat";

import 'katex/dist/katex.min.css';
import TDom from "../../../common/TDom";
import TCommon from "../../../common/TCommon";
import TMatrix from "../../../common/TMatrix";


class Perceptron extends AlgorithmBase{
  constructor(){
    super('Perceptron');
    this.model          = {w:new Mat.zeros([1, 2]),b:0,eta:1.0}

    this.descriptionUrl = `${this.homepage}/data/slm/ch2/README.md`;
    this.datasetUrl     = `${this.homepage}/data/slm/ch2/dataset2.dat`;
  }

  init(){ 
    this.reset();

    this.initTrainDS();
  }

  reset(){
    this.dataset = undefined;
    this.trainDS = undefined;
    this.model   = {w:new Mat.zeros([1, 2]),b:0,eta:1.0}

    this.clear();

    this.initDescription();
    this.updateModel();
  }
  
  // 模型UI
  initModelUI(){
    this.addLabel(this.domElementList.model, '学习率:');
    this.etaInput = this.addFloatInput(
      this.domElementList.model, this.model.eta, 
      (event)=>{
        const val = parseFloat(event.target.value);
        if(val<0){
          this.model.eta = 0
          event.target.value = 0
        }
        else this.model.eta = val;
        this.updateModel();
      });

    this.modelDiv = this.addDiv(this.domElementList.model);
  }

  // 渲染模型到页面
  updateModel(w, b){

    if(w === undefined) w = this.model.w;
    if(b === undefined) b = this.model.b;

    const modelString = String.raw
    `$$ \begin{aligned} w&:=[${w}] \\ b&:=${b} \\ \eta&:=${this.model.eta}\end{aligned} $$`

    mdRender(`${modelString}`, this.outputDiv);
    // 显示数据集
    if(this.trainDS){
      ReactDom.unmountComponentAtNode(this.inputDiv);
      ReactDom.render(
        <React.StrictMode>
          <MatView2D 
            Mat     = {new TMatrix.Mat(this.trainDS.Mat)}
            Labels  = {this.trainDS.Labels}
            XRange  = {[0, 10]}
            YRange  = {[0, 10]}
            Separator = { {type:'line', w:w.toVector(), b:b} }
            PointStyleDict = {this.pointStyleDict}
          />
        </React.StrictMode>,
        this.inputDiv
      );
    }
  }

  // 输入数据UI
  initInputUI(){

    // 文件选择器
    const fileSelector      = document.createElement('input');
    fileSelector.type       = 'file';
    fileSelector.hidden     = true;
    fileSelector.onchange   = this.handleFileChange.bind(this);   
    
    // this.addButton(this.domElementList.input, "获取服务器数据", this.initTrainDS.bind(this));
    TDom.createButton(this.domElementList.input, "获取服务器数据", this.initTrainDS.bind(this));
    this.addButton(this.domElementList.input, "使用本地数据", this.openFileSelector.bind(this));
    this.domElementList.input.appendChild(fileSelector);
    this.addButton(this.domElementList.input, "训练", this.train.bind(this));
    this.playButton = this.addButton(this.domElementList.input, "训练动画", this.trainAnimation.bind(this));
    this.playButton.hidden = true;

    this.labelTrainTips = this.addLabel(this.domElementList.input, '');
    this.inputDiv       = this.addDiv(this.domElementList.input);

    this.fileSelector  = fileSelector;
  }

  // 输出UI
  initOutputUI(){
    this.outputDiv   = this.addDiv(this.domElementList.output);
    this.trainLogDiv = this.addDiv(this.domElementList.output);
  }

  // 获取描述md文件
  initDescription(){
    TDom.getDataFromUrl(this.descriptionUrl, (txt)=>{
      mdRender(txt, this.descriptionDiv);
    })
    // fetch(this.descriptionUrl).then((response)=>{
    //   return response.text();
    // }).then((txt)=>{

    //   this.reandMD(txt, this.descriptionDiv)
    // })
  }

  // 获取服务器数据集
  initTrainDS(){
    this.prepare(this.datasetUrl);
  }

  // 准备数据训练数据
  prepare(url){
    TDom.getDataFromUrl(url, 
      (txt)=>{
      this.dataset = txtToMatrix(txt);
      this.trainDS = TCommon.loadDataset(this.dataset);
      this.pointStyleDict = createRandomPointStyleDict(this.trainDS.Labels);
      this.updateModel();
    })
  }

  openFileSelector(){
    this.fileSelector.click();
  }
  // 获取文件路径加载
  handleFileChange(event){
    const files = event.target.files;

    for(let i = 0, il = files.length; i < il; i++){
      let reader = new FileReader();
      reader.onload = (e) => {      
        this.prepare(e.target.result);
        if(i === files.length - 1){
          event.target.value = '';
        }
      };
      reader.readAsDataURL(files[i])
    }
  }

  // 训练
  train(){
    this.playButton.hidden = true;
    const trainDS = this.trainDS;
    if(!trainDS) return;

    let X = new Mat.Mat(trainDS.Mat);
    let y = new Mat.Mat(trainDS.Labels);
    console.log(trainDS);
    console.log(X,y);
    var w = new Mat.zeros([1, 2]);
    var b = 0.0;
    var eta = parseFloat(this.model.eta);
    var count = 0;
    var isDone = false;
    var trainHistory = [];    // 训练历史
    var trainMDLog   = "## 迭代过程\r\n";    // 训练日志-使用MD编写
    trainMDLog += "|迭代次数|误分类点|$w$|$b$|$w\\cdot x+b$|\r\n"
    trainMDLog += "|:---:|:---:|:---:|:---:|:---:|\r\n";
    trainMDLog += "|0||0|0|0|\r\n";

    while(!isDone && count < 100){
      count++;
      isDone = true;
      let i = 0, il = X.length;
      for(; i < il; i++){
        const rst = y[i]*(Mat.dot(X.slice(i), w) + b);
        if(rst <= 0){
          w = Mat.add(w, Mat.multiply(X.slice(i), y[i]*eta));
          b = b + parseFloat(y[i]*eta);
          isDone = false;
          trainHistory.push({ errPoint:i, w:w, b:b, time:new Date()} );
          console.log(w);
          let tmp = `$${w[0][0]}x^{(1)}${w[0][1]>=0?'+':''}${w[0][1]}x^{(${2})}${b>=0?'+':''}${b}$`;
          if(w[0][0] === 0 && w[0][1] === 0){
            tmp = b;
          }

          trainMDLog += String.raw`|${count}|$x_${i+1}$|$(${w[0]})^T$|${b}|${tmp}|`+"\r\n";
          break;
        }
      }
    }

    let tmp = `$${w[0][0]}x^{(1)}${w[0][1]>=0?'+':''}${w[0][1]}x^{(${2})}${b>=0?'+':''}${b}$`;
    if(w[0][0] === 0 && w[0][1] === 0){
      tmp = b;
    }

    trainMDLog += String.raw`|${count}|0|$(${w[0]})^T$|${b}|${tmp}|`+"\r\n";

    this.model.w = w;
    this.model.b = b;

    this.updateModel();
    this.labelTrainTips.innerText = '训练完毕';
    this.playButton.hidden = false;
    this.trainHistory = trainHistory;

    mdRender(trainMDLog, this.trainLogDiv);
  }

  trainAnimation(){
    if(this.trainHistory && !this.animation){
      this.trainHistoryIndex = 0;
      this.animation  = setInterval(this.playHistory.bind(this), 100);
    }
  }

  playHistory(){
    if(this.trainHistoryIndex < this.trainHistory.length){
      console.log(this.trainHistory);
      const lastest = this.trainHistory[this.trainHistoryIndex];
      this.updateModel(lastest.w, lastest.b);
      this.labelTrainTips.innerText = `训练时间:${lastest.time.toString()}`;
      this.trainHistoryIndex += 1;
    }else{
      clearInterval(this.animation);
      this.animation = '';
      this.labelTrainTips.innerText = `播放完毕`;
    }
  }
}


export default Perceptron;